package net.crowtheries.wirelessrouting;

import java.util.HashMap;
import java.util.HashSet;


import ptolemy.actor.TypedAtomicActor;
import ptolemy.data.BooleanToken;
import ptolemy.data.IntToken;
import ptolemy.data.RecordToken;
import ptolemy.data.StringToken;
import ptolemy.data.Token;
import ptolemy.data.expr.StringParameter;
import ptolemy.domains.wireless.kernel.WirelessIOPort;
import ptolemy.kernel.CompositeEntity;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.NameDuplicationException;

public class RoutesManager extends TypedAtomicActor{
	public RoutesManager(CompositeEntity container,String name) throws IllegalActionException, NameDuplicationException{
		super(container,name);
		reqHop=new WirelessIOPort(this,"reqHop",true,false);
		nxtHop=new WirelessIOPort(this,"nxtHop",false,true);
		updateEntries=new WirelessIOPort(this,"updateEntries",true,false);
		reqRoute=new WirelessIOPort(this,"reqRoute",false,true);
        entryRequestStatus=new WirelessIOPort(this,"entryRequestStatus",false,true);
		
		_routingTable=new HashMap<String, Object[]>();
	}
	
	///////// Ports /////
	/**
	 * 
	 */
	WirelessIOPort reqHop;
	WirelessIOPort nxtHop;
	WirelessIOPort reqRoute;
	WirelessIOPort updateEntries;
    /**
     * Returns the 
     */
    WirelessIOPort entryRequestStatus;
	
	///////// Private vars //////
	/**
	 * Contains the routes :
	 * key=DST_ADR
	 * Value= [ NXT_HOP , DST_SEQ , HOPCOUNT, LIFE ]
	 */
	HashMap<String, Object[]> _routingTable;
	
	/**
	 * Contains a cache of all the requests coming from FH
	 * it'll be to avoid sending "useless" messages to FH 
	 * for example when we make reverse routes
	 */
	HashSet<StringToken> _cacheIP=new HashSet<StringToken>();
	
	String _localIP;
	
	public void initialize() throws IllegalActionException{
		StringParameter localip=(StringParameter)this.getContainer().getAttribute("localIP");
		_localIP=localip.stringValue();
	}
	
	public void fire() throws IllegalActionException{
		
		/*
		 * If we get a request for a route
		 */
		if(reqHop.hasToken(0)){			
			StringToken dstIP=(StringToken)reqHop.get(0);
			
			//we add the IP to the Cache
			_cacheIP.add(dstIP);
			
			//if the destination IP is in te table we send it directly to the "nxtHop" port to be used by the FlowHandler 
			if(_routingTable.get(dstIP.stringValue())!=null){
				//we send the DSTIP and the NEXTHOP
				RecordToken token=new RecordToken(
						new String[]{"DSTIP","NEXTHOP"},
						new Token[]{
							new StringToken(dstIP.stringValue()),
							new StringToken((String)_routingTable.get(dstIP.stringValue())[0])
						});
				nxtHop.send(0, token);
			}
			
			//if we dont have it, we forward the ip to the protocol manager so he handles it by making the RREQ...
			else
				reqRoute.send(0, dstIP);
		}
		
		if(updateEntries.hasToken(0)){
            RecordToken operation=(RecordToken)updateEntries.get(0);
            int opCode=((IntToken)operation.get("OPC")).intValue();
            switch(opCode){
                //Code for adding an entry
                case 1:
                    break;

                //Code for Deleting an entry
                case 2:
                    break;

                //Code for Updating (it's both add/update) an entry
                case 3:
                    RecordToken message=(RecordToken)operation.get("MSG");
                    String IP=((StringToken)message.get("IP")).stringValue();
                    String NEXTHOP=((StringToken)message.get("NEXTHOP")).stringValue();
                    int DSTSEQ=((IntToken)message.get("DSTSEQ")).intValue();
                    int HOPCOUNT=((IntToken)message.get("HOPCOUNT")).intValue();
                    if(_routingTable.containsKey(IP)){
                        //UPDATE
                    	if(((Integer)_routingTable.get(IP)[2])>=HOPCOUNT)
                    		this.updateEntry(IP, NEXTHOP, DSTSEQ, HOPCOUNT);
                    } else {
                        //ADD
                        this.addEntry(IP, NEXTHOP, DSTSEQ, HOPCOUNT);
                    }
                    break;

                //Code for requesting whether the entry exists or not
                case 4:
                	IP=((StringToken)operation.get("MSG")).stringValue();
                    System.out.println("IP="+IP);
                	boolean hasEntry=_routingTable.containsKey(IP);
                    entryRequestStatus.send(0,
                            new RecordToken(new String[]{
                                "IP",
                                "HOPCOUNT",
                                "NEXT_HOP"
                            },new Token[]{
                                (StringToken)operation.get("MSG"),
                                new IntToken(hasEntry? this.getHOPCOUNT(IP):-1),
                                new StringToken(hasEntry? this.getNEXTHOP(IP):null)
                            }));
                            
                    break;
                    
                //Code to Inform the FLowHandler that we got a route to the destination
                case 5:
                	IP=((StringToken)operation.get("MSG")).stringValue();
                	System.out.println("blabla, going to Flow Handler: "+(String)_routingTable.get(IP)[0]);
                	nxtHop.send(0,
                			new RecordToken(
                					new String[]{
                							"NEXTHOP",
                							"DSTIP"
                					},
                					new Token[]{
                							new StringToken((String)_routingTable.get(IP)[0]),
                							(StringToken)operation.get("MSG")
                					}
                            )
                            );
                	break;
                default:
                    //do Something?
            }
		}
	}
	private String getNEXTHOP(String IP){
		return (String)_routingTable.get(IP)[0];
	}
	private Integer getHOPCOUNT(String IP){
		return (Integer)_routingTable.get(IP)[2];
	}
    private void addEntry(String ORIGIP, String NEXTHOP, int DSTSEQ, int HOPCOUNT){
        this._routingTable.put(ORIGIP, new Object[]{
            NEXTHOP,
            DSTSEQ,
            HOPCOUNT,
            //TODO: manage LIFE properly
            255
        });
    }

    private void updateEntry(String ORIGIP, String NEXTHOP, int DSTSEQ, int HOPCOUNT){
        this._routingTable.remove(ORIGIP);
        this.addEntry(ORIGIP, NEXTHOP, DSTSEQ, HOPCOUNT);
    }
}
